<template>
  <component
    ref="formEl"
    :is="formRegister ? 'w-form-element' : 'div'"
    v-bind="
      formRegister && {
        validators,
        inputValue: selectionString,
        disabled,
        readonly
      }
    "
    v-model:valid="valid"
    @reset="onReset"
    :class="classes"
    ><template v-if="labelPosition === 'left'"
      ><label
        class="w-select__label w-select__label--left w-form-el-shakable"
        v-if="$slots.default"
        :for="`w-select--${_.uid}`"
      >
        <slot></slot> </label
      ><label
        class="w-select__label w-select__label--left w-form-el-shakable"
        v-else-if="label"
        :for="`w-select--${_.uid}`"
        v-html="label"
      ></label
    ></template>
    <w-menu
      v-model="showMenu"
      :menu-class="`w-select__menu ${menuClass || ''}`"
      transition="slide-fade-down"
      :detach-to="
        (menuProps || {}).detachTo !== undefined
          ? (menuProps || {}).detachTo
          : '.w-app'
      "
      align-left="align-left"
      custom="custom"
      min-width="activator"
      v-bind="menuProps || {}"
    >
      <template #activator="{ on }">
        <!-- TODO: ADD v-on="on" -->
        <div
          v-on="on"
          class="w-select__selection-wrap"
          ref="selection-wrap"
          @click="!disabled && !readonly && openMenu()"
          role="button"
          aria-haspopup="listbox"
          :aria-expanded="showMenu ? 'true' : 'false'"
          :aria-owns="`w-select-menu--${_.uid}`"
          :aria-activedescendant="`w-select-menu--${_.uid}_item-1`"
          :class="inputWrapClasses"
        >
          <w-icon
            class="w-select__icon w-select__icon--inner-left"
            v-if="innerIconLeft"
            tag="label"
            :for="`w-select--${_.uid}`"
            @click="$emit('click:inner-icon-left', $event)"
            >{{ innerIconLeft }}</w-icon
          >
          <div class="w-select__selection-slot" v-if="$slots.selection">
            <slot
              name="selection"
              :item="multiple ? inputValue : inputValue[0]"
            ></slot>
          </div>
          <!-- ref="selection-input" -->
          <input
            class="w-select__selection"
            ref="SelectionInputRef"
            type="text"
            :value="$slots.selection ? '' : selectionString"
            @focus="!disabled && !readonly && onFocus($event)"
            @blur="onBlur"
            @keydown.escape="!disabled && !readonly && closeMenu()"
            @keydown.space.prevent="!disabled && !readonly && openMenu()"
            :id="`w-select--${_.uid}`"
            :placeholder="(!$slots.selection && placeholder) || null"
            :disabled="disabled || null"
            readonly="readonly"
            aria-readonly="true"
            :required="required || null"
            autocomplete="off"
          /><input
            v-for="(val, i) in inputValue.length ? inputValue : [{}]"
            :key="i"
            type="hidden"
            :value="val.value || ''"
            :name="inputName + (multiple ? '[]' : '')"
          /><template v-if="labelPosition === 'inside' && showLabelInside"
            ><label
              class="w-select__label w-select__label--inside w-form-el-shakable"
              v-if="$slots.default"
              :for="`w-select--${_.uid}`"
              :class="
                isFocused && {
                  [valid === false ? 'error' : color]: color || valid === false
                }
              "
            >
              <slot></slot> </label
            ><label
              class="w-select__label w-select__label--inside w-form-el-shakable"
              v-else-if="label"
              :for="`w-select--${_.uid}`"
              v-html="label"
              :class="
                isFocused && {
                  [valid === false ? 'error' : color]: color || valid === false
                }
              "
            ></label
          ></template>
          <w-icon
            class="w-select__icon w-select__icon--inner-right"
            v-if="innerIconRight"
            tag="label"
            :for="`w-select--${_.uid}`"
            @click="$emit('click:inner-icon-right', $event)"
            >{{ innerIconRight }}</w-icon
          >
        </div>
      </template>
      <!-- ref = w-list -->
      <w-list
        ref="WlistRef"
        :model-value="inputValue"
        @update:model-value="onInput"
        @item-click="$emit('item-click', $event)"
        @item-select="onListItemSelect"
        @keydown:enter="noUnselect && !multiple && closeMenu()"
        @keydown:escape="closeMenu"
        :items="selectItems"
        :multiple="multiple"
        arrows-navigation="arrows-navigation"
        return-object="return-object"
        :add-ids="`w-select-menu--${_.uid}`"
        :no-unselect="noUnselect"
        :selection-color="selectionColor"
        role="listbox"
        tabindex="-1"
        ><template #item="{ item, selected, index }">
          <slot name="item" :item="item" :selected="selected" :index="index">{{
            item[itemLabelKey]
          }}</slot>
        </template></w-list
      > </w-menu
    ><template v-if="labelPosition === 'right'"
      ><label
        class="w-select__label w-select__label--right w-form-el-shakable"
        v-if="$slots.default"
        :for="`w-select--${_.uid}`"
      >
        <slot></slot> </label
      ><label
        class="w-select__label w-select__label--right w-form-el-shakable"
        v-else-if="label"
        :for="`w-select--${_.uid}`"
        v-html="label"
      ></label
    ></template>
  </component>
</template>

<script>
/**
 * @todo Share the common parts between w-input, w-textarea & w-select.
 **/

import { ref, computed, watch, getCurrentInstance } from "vue";
export default {
  name: "w-select",
  inject: { formRegister: { default: null } },
  props: {
    items: { type: Array, required: true },
    modelValue: {}, // v-model on selected item if any.
    multiple: { type: Boolean },
    placeholder: { type: String },
    label: { type: String },
    labelPosition: { type: String, default: "inside" },
    innerIconLeft: { type: String },
    innerIconRight: { type: String, default: "wi-triangle-down" },
    // When label is inside, allows to move the label above on focus or when filled.
    staticLabel: { type: Boolean },
    itemLabelKey: { type: String, default: "label" }, // Name of the label field.
    itemValueKey: { type: String, default: "value" }, // Name of the value field.
    itemClass: { type: String },
    menuClass: { type: String },
    color: { type: String, default: "primary" }, // Applies to all the items.
    selectionColor: { type: String, default: "primary" }, // Applies to the selected items only.
    bgColor: { type: String }, // Applies to all the items.
    outline: { type: Boolean },
    round: { type: Boolean },
    shadow: { type: Boolean },
    tile: { type: Boolean },
    dark: { type: Boolean },
    returnObject: { type: Boolean },
    // By default you can unselect a list item by re-selecting it.
    // Allow preventing that on single selection lists only.
    noUnselect: { type: Boolean },
    menuProps: { type: Object },
    // Also name, disabled, readonly, required and validators in the mixin.
    name: { type: String }, // When sending data through form.
    disabled: { type: Boolean },
    readonly: { type: Boolean },
    required: { type: Boolean },
    validators: { type: Array }
  },

  emits: [
    "input",
    "update:modelValue",
    "focus",
    "blur",
    "item-click",
    "item-select",
    "click:inner-icon-left",
    "click:inner-icon-right"
  ],

  setup(props, ctx) {
    const instance = getCurrentInstance();
    const inputValue = ref([]);
    const showMenu = ref(false);
    const menuMinWidth = ref(0);
    const isFocused = ref(false);
    const valid = ref(null);
    // refs
    const WlistRef = ref(null);
    const SelectionInputRef = ref(null);

    const selectItems = computed(() => {
      return props.items.map((item, i) => {
        const obj = { ...item }; // Don't modify the original.

        // If no value is set on the item, add one from its label, or from its index. the result is
        // store in the value attribute for easy use in the w-list component (which tries the same logic).
        obj.value =
          obj[props.itemValueKey] === undefined
            ? obj[props.itemLabelKey] || i
            : obj[props.itemValueKey];
        obj.index = i;
        return obj;
      });
    });
    const hasValue = computed(() => {
      return Array.isArray(inputValue.value)
        ? inputValue.value.length
        : inputValue.value !== null;
    });
    const hasLabel = computed(() => {
      return props.label || ctx.slots.default;
    });
    const showLabelInside = computed(() => {
      return !props.staticLabel || (!hasValue.value && !props.placeholder);
    });
    const selectionString = computed(() => {
      return (
        inputValue.value &&
        inputValue.value
          .map(item =>
            item[props.itemValueKey] !== undefined
              ? item[props.itemLabelKey]
              : item[props.itemLabelKey] !== undefined
              ? item[props.itemLabelKey]
              : item
          )
          .join(", ")
      );
    });
    const classes = computed(() => {
      return {
        "w-select": true,
        "w-select--disabled": props.disabled,
        "w-select--readonly": props.readonly,
        [`w-select--${hasValue.value ? "filled" : "empty"}`]: true,
        "w-select--focused": isFocused.value,
        "w-select--dark": props.dark,
        "w-select--floating-label":
          hasLabel.value &&
          props.labelPosition === "inside" &&
          !props.staticLabel &&
          !(props.readonly && !hasValue.value),
        "w-select--no-padding":
          !props.outline && !props.bgColor && !props.shadow && !props.round,
        "w-select--has-placeholder": props.placeholder,
        "w-select--inner-icon-left": props.innerIconLeft,
        "w-select--inner-icon-right": props.innerIconRight,
        "w-select--open": showMenu.value
      };
    });
    const inputWrapClasses = computed(() => {
      return {
        [valid.value === false ? "error" : props.color]:
          props.color || valid.value === false,
        [`${props.bgColor}--bg`]: props.bgColor,
        "w-select__selection-wrap--round": props.round,
        "w-select__selection-wrap--tile": props.tile,
        // Box adds a padding on input. If there is a bgColor or shadow, a padding is needed.
        "w-select__selection-wrap--box":
          props.outline || props.bgColor || props.shadow,
        "w-select__selection-wrap--underline": !props.outline,
        "w-select__selection-wrap--shadow": props.shadow,
        "w-select__selection-wrap--no-padding":
          !props.outline && !props.bgColor && !props.shadow && !props.round
      };
    });
    const inputName = computed(() =>{
      props.name || `${instance.type.name}--${instance.type.uid || instance.uid}`
    })

    // created
    inputValue.value = checkSelection(props.modelValue);
    function onFocus(e) {
      isFocused.value = true;
      ctx.emit("focus", e);
    }

    function onBlur(e) {
      isFocused.value = false;
      ctx.emit("blur", e);
    }

    // The items are given by the w-list component.
    function onInput(items) {
      inputValue.value = items === null ? [] : props.multiple ? items : [items];
      // Return the original items when returnObject is true (no `value` if there wasn't),
      // or the the item value otherwise.
      items = inputValue.value.map(item =>
        props.returnObject ? props.items[item.index] : item.value
      );

      // If single selection, close the menu after selecting a value (keyboard selection).
      if (!props.multiple) {
        closeMenu();
      }

      // Emit the selection to the v-model.
      // Note: this.inputValue is always an array of objects that have a `value`.
      const selection = props.multiple ? items : items[0];
      ctx.emit("update:modelValue", selection);
      ctx.emit("input", selection);
    }

    function onListItemSelect(e) {
      ctx.emit("item-select", e);
      // Close menu when clicking a selected item.
      if (props.noUnselect && !props.multiple) {
        closeMenu();
      }
    }

    function onReset() {
      inputValue.value = [];
      // Emit the selection to the v-model.
      // Note: this.inputValue is always an array of objects that have a `value`.
      const selection = props.multiple ? [] : null;
      ctx.emit("update:modelValue", selection);
      ctx.emit("input", selection);
    }

    // Convert the received items selection to array if it is a unique value.
    // Also accept objects if returnObject is true.
    // In any case, always end up with an array.
    function checkSelection(items) {
      items = Array.isArray(items) ? items : items ? [items] : [];
      // `selectItems` items always have a value.
      const allValues = selectItems.value.map(item => item.value);

      return items
        .map(item => {
          let value = item;
          if (typeof item === "object") {
            value =
              item[props.itemValueKey] !== undefined
                ? item[props.itemValueKey]
                : item[props.itemLabelKey] !== undefined
                ? item[props.itemLabelKey]
                : item;
          }

          return selectItems.value[allValues.indexOf(value)];
        })
        .filter(item => item !== undefined);
    }

    // // Open the dropdown selection list.
    function openMenu() {
      showMenu.value = true;
      // Set the focus on the first option.
      setTimeout(() => {
        const itemIndex = inputValue.value.length
          ? inputValue.value[0].index
          : 0; // Real index starts at 0.
        // User visible index starts at 1.
        WlistRef.value.$el
          .querySelector(
            `#w-select-menu--${instance.ctx._.uid}_item-${itemIndex + 1}`
          )
          .focus();
      }, 100);
    }

    // // Close the dropdown selection list.
    function closeMenu() {
      if ((props.menuProps || {}).hideOnMenuClick === false) return;

      showMenu.value = false;
      // Set the focus back on the main w-select input.
      setTimeout(() => SelectionInputRef.value.focus(), 50);
    }

    watch(
      () => props.modelValue,
      value => {
        if (value !== inputValue.value) {
          inputValue.value = checkSelection(value);
        }
      }
    );

    return {
      inputValue,
      showMenu,
      menuMinWidth,
      isFocused,
      valid,
      // refs
      WlistRef,
      SelectionInputRef,
      // computed
      selectItems,
      hasValue,
      hasLabel,
      showLabelInside,
      selectionString,
      classes,
      inputWrapClasses,
      inputName,
      //methods
      onFocus,
      onBlur,
      onInput,
      onReset,
      onListItemSelect,
      checkSelection,
      openMenu,
      closeMenu
    };
  },

};
</script>

